#include <sobjc-commons/defines.h>
#include <sobjc-commons/builtin_sels.h>
#include "command.h"
#include "context.h"
#include "name.h"

static void write_init_class(objc_class_t, FILE *);
static void write_intern_sel(unsigned long, const char *, FILE *);
static void write_add_methods(FILE *, objc_class_t, BOOL);

objc_line_mark_t objc_current_line_mark     = NULL;
objc_line_mark_t objc_recorded_line_mark    = NULL;
int objc_parsing_column                     = 0;

objc_class_t objc_current_class             = NULL;
BOOL objc_current_class_is_declared         = NO;
BOOL objc_current_class_is_abstract         = NO;
BOOL objc_current_class_is_sealed           = NO;

int objc_nimpl_classes         = 0;
int objc_nimpl_ivars           = 0;
int objc_nimpl_inst_meths      = 0;
int objc_nimpl_class_meths     = 0;

void
objc_record_line_mark(int lineno, const char *file, int flags)
{
  objc_line_mark_t mark;

  mark = ALLOC(struct objc_line_mark);
  mark->lineno = lineno;
  mark->file = objc_strdup(file);
  mark->flags = flags;
  mark->prev = NULL;

  if (objc_recorded_line_mark != NULL) {
    objc_recorded_line_mark->next = mark;
    mark->prev = objc_recorded_line_mark;
  }
  objc_recorded_line_mark = mark;
}

void
objc_push_line_mark(int lineno, const char *file)
{
  objc_line_mark_t mark;

  mark = ALLOC(struct objc_line_mark);
  mark->lineno = lineno;
  mark->file = objc_strdup(file);
  mark->flags = 0;
  mark->prev = NULL;

  if (objc_current_line_mark != NULL) {
    objc_current_line_mark->next = mark;
    mark->prev = objc_current_line_mark;
  }
  objc_current_line_mark = mark;
}

void
objc_pop_line_mark()
{
  objc_line_mark_t mark;

  mark = objc_current_line_mark;
  objc_current_line_mark = mark->prev;
  mark->prev->next = NULL;
}
  
void
objc_write_init_code(FILE *f)
{
  int i;

  fprintf(f, "static void __attribute__((constructor))\n"
          "__objc_init_module()\n{\n");
  if (objc_nimpl_classes > 0) {
    fprintf(f, "  static struct objc_proto_class "
               TEMP_CLASS "[%d];\n", objc_nimpl_classes);
    if (objc_nimpl_ivars > 0)
      fprintf(f, "  static struct objc_proto_ivar "
                 TEMP_IVAR "[%d];\n", objc_nimpl_ivars);
    if (objc_nimpl_inst_meths > 0)
      fprintf(f, "  static struct objc_proto_method "
                 TEMP_INST_METH "[%d];\n", objc_nimpl_inst_meths);
    if (objc_nimpl_class_meths > 0)
      fprintf(f, "  static struct objc_proto_method "
                 TEMP_CLASS_METH "[%d];\n", objc_nimpl_class_meths);
  }

  /* initialize runtime */
  fprintf(f, "\n  objc_init_runtime();\n\n");

  /* initialize constant strings */
  if (objc_main_opts.cstr_class)
    fprintf(f, "  _objc_set_const_str_class_name(\"%s\");\n\n",
            objc_get_const_str_class_name());

  /* intern selectors */
  objc_iter_sels((objc_sel_iter_f)write_intern_sel, (void *)f);
  fprintf(f, "\n");

  /* register classes */
  objc_nimpl_classes = 0;
  objc_nimpl_ivars = 0;
  objc_nimpl_inst_meths = 0;
  objc_nimpl_class_meths = 0;
  objc_iter_classes((objc_class_iter_f)write_init_class, (void *)f);

  fprintf(f, "}\n");
  fclose(f);
}

static void
write_intern_sel(unsigned long n, const char *name, FILE *f)
{
  if (n != OBJC_NOT_USED_SEL) {
    fprintf(f, "  " SEL_TABLE_VAR "[%lu] = ", n);
    if (objc_get_builtin_sel(name) != NULL)
      fprintf(f, "(SEL)objc_get_builtin_sel(\"%s\");\n", name);
    else
      fprintf(f, "objc_register_sel(\"%s\");\n", name);
  }
}

static void
write_init_class(objc_class_t cls, FILE *f)
{
  objc_ivar_list_t ivars;
  objc_ivar_t ivar;
  int nivars, ninst_meths, nclass_meths;

  /* interface only */
  if (!cls->has_impl)
    return;

  /* initialize prototype class */
  fprintf(f, "  " TEMP_CLASS "[%d].name = \"%s\";\n", objc_nimpl_classes, cls->name);

  fprintf(f, "  " TEMP_CLASS "[%d].super_name = ", objc_nimpl_classes);
  if (cls->superclass == NULL)
    fprintf(f, NULL_S ";\n");
  else 
    fprintf(f, "\"%s\";\n", cls->superclass->name);

  nivars = objc_ivar_list_get_size(cls->ivars);
  fprintf(f, "  " TEMP_CLASS "[%d].nivars = %d;\n",
          objc_nimpl_classes, nivars);
  fprintf(f, "  " TEMP_CLASS "[%d].ivars = ", objc_nimpl_classes);
  if (nivars)
    fprintf(f, "&" TEMP_IVAR "[%d];\n", objc_nimpl_ivars);
  else
    fprintf(f, NULL_S ";\n");

  ninst_meths = objc_method_list_get_size(cls->inst_meths);
  fprintf(f, "  " TEMP_CLASS "[%d].ninst_meths = %d;\n",
          objc_nimpl_classes, ninst_meths);
  fprintf(f, "  " TEMP_CLASS "[%d].inst_meths = ", objc_nimpl_classes);
  if (ninst_meths)
    fprintf(f, "&" TEMP_INST_METH "[%d];\n", objc_nimpl_inst_meths);
  else
    fprintf(f, NULL_S ";\n");

  nclass_meths = objc_method_list_get_size(cls->class_meths);
  fprintf(f, "  " TEMP_CLASS "[%d].nclass_meths = %d;\n",
          objc_nimpl_classes, nclass_meths);
  fprintf(f, "  " TEMP_CLASS "[%d].class_meths = ", objc_nimpl_classes);
  if (nclass_meths)
    fprintf(f, "&" TEMP_CLASS_METH "[%d];\n", objc_nimpl_class_meths);
  else
    fprintf(f, NULL_S ";\n");

  fprintf(f, "  " TEMP_CLASS "[%d].ninst_dcodes = %d;\n",
          objc_nimpl_classes,
          cls->inst_dcodes ? cls->inst_dcodes->num_entries : 0);
  fprintf(f, "  " TEMP_CLASS "[%d].nclass_dcodes = %d;\n",
          objc_nimpl_classes,
          cls->class_dcodes ? cls->class_dcodes->num_entries : 0);
  fprintf(f, "\n");

  /* add instance variables */
  ivars = cls->ivars;
  while (ivars) {
    ivar = ivars->car;
    fprintf(f, "  " TEMP_IVAR "[%d].name = \"%s\";\n",
            objc_nimpl_ivars, ivar->name);
    fprintf(f, "  " TEMP_IVAR "[%d].size = sizeof(%s);\n",
            objc_nimpl_ivars, ivar->type_name);
    fprintf(f, "  " TEMP_IVAR "[%d].type = \"%s\";\n",
            objc_nimpl_ivars,  ivar->enc->full_code);
    objc_nimpl_ivars++;
    ivars = ivars->cdr;
  }
  fprintf(f, "\n");

  /* add instance and class methods */
  write_add_methods(f, cls, YES);
  write_add_methods(f, cls, NO);

  /* registser prototype class */
  fprintf(f, "  objc_register_proto_class(&" TEMP_CLASS "[%d]);\n",
          objc_nimpl_classes);
  objc_nimpl_classes++;
}

static void
write_add_methods(FILE *f, objc_class_t cls, BOOL is_inst)
{
  objc_method_list_t meths;
  objc_method_t meth;
  st_table_t dcodes;
  int *nmeths;
  const char *var;

  if (is_inst) {
    meths = cls->inst_meths;
    dcodes = cls->inst_dcodes;
    nmeths = &objc_nimpl_inst_meths;
    var = TEMP_INST_METH;
  } else {
    meths = cls->class_meths;
    dcodes = cls->class_dcodes;
    nmeths = &objc_nimpl_class_meths;
    var = TEMP_CLASS_METH;
  }

  while (meths) {
    meth = meths->car;
    if (meth->has_impl) {
      fprintf(f, "  %s[%d].name = objc_get_sel(\"%s\");\n", var, *nmeths,
              meth->sig->name);
      fprintf(f, "  %s[%d].imp = (IMP)%s;\n", var, *nmeths, meth->imp);
      fprintf(f, "  %s[%d].types = \"%s\";\n", var, *nmeths,
              meth->sig->enc->full_code);
      fprintf(f, "  %s[%d].dcode = %d;\n", var, *nmeths,
              objc_get_dispatch_code(cls, meth->sig->name, is_inst));
      fprintf(f, "  %s[%d].is_private = %d;\n", var, *nmeths,
              meth->vsbl == OBJC_PRIVATE);
      fprintf(f, "  %s[%d].is_sealed = %d;\n", var, *nmeths,
              meth->is_sealed);
    }
    (*nmeths)++;
    meths = meths->cdr;
  }
  fprintf(f, "\n");
}

